package org.datascooter.impl;

import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import org.datascooter.bundle.DSMapper;
import org.datascooter.bundle.EntityBundle;
import org.datascooter.bundle.attribute.PersistAttribute;
import org.datascooter.exception.DataScooterException;
import org.datascooter.exception.EntityNotMappedException;
import org.datascooter.utils.SnipUtils;
import org.datascooter.utils.policy.ChildPolicy;
import org.datascooter.utils.policy.Composer;
import org.datascooter.utils.policy.Constraint;
import org.datascooter.utils.policy.Function;
import org.datascooter.utils.policy.SnipType;

/**
 * This class is just a data collector for building Snip - the main data transfer object
 * 
 * @author nemo
 * 
 */
public final class DataSnip extends Data {

	private static final long serialVersionUID = 8422873005792757515L;
	public String entityName;
	private SnipType snipType;
	public String parentId;
	public String extraAttribute = SnipUtils.NN;
	public boolean joinFromChild = true;
	public Condition where;
	public Condition what;
	public Object objectId;
	public String whereId;
	public String whatId;
	public String sqlClause = SnipUtils.NN;
	public String[] columnNames;
	public boolean byResult = false;
	//
	public DataSnip parent;
	public transient EntityBundle bundle;
	public int fromRow = 1;
	public int fetchSize = 0;
	public Boolean orderASC;
	private Function function;
	public boolean distinct = false;
	public int parentIndex = -1;
	public int hashCode = -1;
	public boolean casheSnip = true;
	private ChildPolicy childPolicy = ChildPolicy.ONLY_THIS;
	public boolean excludeExtraList = true;
	public final List<String> extraList = new ArrayList<String>(0);
	public List<DataSnip> joinList = new ArrayList<DataSnip>(0);
	public List<DataSnip> fetchList = new ArrayList<DataSnip>(0);
	private transient DataManager dataManager;

	public DataSnip() {
	}

	public DataSnip(String entityName, SnipType type) {
		setSnipType(type);
		this.setCreated(new Date());
		this.entityName = entityName;
	}

	public DataSnip(String joinAttribute, String entityName, boolean joinFromChild, SnipType type, boolean byResult) {
		this(entityName, type);
		extraAttribute = joinAttribute;
		this.joinFromChild = joinFromChild;
		this.byResult = byResult;
	}

	public DataSnip(String entity, SnipType type, int fromRow, int fetchSize) {
		this(entity, type);
		this.fromRow = fromRow;
		this.fetchSize = fetchSize;
	}

	public DataSnip exclude(String attributeName) {
		extraList.add(attributeName);
		excludeExtraList = true;
		return this;
	}

	public DataSnip page(int fromRow, int fetchSize) {
		this.fromRow = fromRow;
		this.fetchSize = fetchSize;
		return this;
	}

	public DataSnip include(String attributeName) {
		extraList.add(attributeName);
		excludeExtraList = false;
		return this;
	}

	public DataSnip where(Condition snipCondition) {
		if (where == null) {
			where = snipCondition;
			whereId = snipCondition.getId();
		} else {
			where.addChild(snipCondition);
		}
		snipCondition.snipId = getId();
		snipCondition.isWhere = true;
		if (what != null) {
			Iterator<LogicValue> iterator = what.valueList.iterator();
			while (iterator.hasNext()) {
				LogicValue next = iterator.next();
				if (snipCondition.valueList.size() > 0 && next.equals(snipCondition.valueList.get(0))) {
					iterator.remove();
					break;
				}
			}
		}
		return this;
	}

	public DataSnip up() {
		return parent;
	}

	public Condition and() {
		Condition condition = new Condition(Composer.AND);
		where(condition);
		return condition;
	}

	public Condition or() {
		Condition condition = new Condition(Composer.OR);
		where(condition);
		return condition;
	}

	public DataSnip where(String attributeName, Object value, Constraint constraint) {
		return where(new Condition(attributeName, constraint, value));
	}

	public DataSnip where(String attributeName, Object value) {
		return where(attributeName, value, Constraint.EQUAL);
	}

	public DataSnip whereId(Object object) {
		PersistAttribute id2 = DSMapper.getId(entityName);
		if (id2 == null) {
			throw new DataScooterException("*** Id not found  " + entityName);
		}
		return where(id2.name, object);
	}

	public DataSnip like(String attributeName, String likeStr) {
		return where(new Condition(attributeName, Constraint.LIKE, likeStr));
	}

	public DataSnip what(String attributeName, Object value) {
		if (what == null) {
			what = new Condition(attributeName, Constraint.EQUAL, value);
			whatId = what.getId();
			what.snipId = getId();
		} else {
			what.equal(attributeName, value);
		}
		return this;
	}

	public DataSnip join(DataSnip dataSnip) {
		if (dataSnip != null) {
			joinList.add(dataSnip);
			dataSnip.setParent(this);
			dataSnip.setDataManager(dataManager);
		}
		return dataSnip;
	}

	public DataSnip join(String childEntity) {
		return join(new DataSnip(childEntity, SnipType.NONE));
	}

	/**
	 * Puprosed for joining entities with many different fields. When Entity has many references to other Entity and you must select only one
	 * 
	 * @param joinAttribute
	 * @param childEntity
	 * @param joinFromChildIn
	 *            = true if joinAttribute belongs of the childEntity
	 * @return DataSnip
	 * @throws EntityNotMappedException
	 */
	public DataSnip join(String joinAttribute, String childEntity, boolean joinFromChildIn) {
		return join(new DataSnip(joinAttribute, childEntity, joinFromChildIn, SnipType.NONE, false));
	}

	public DataSnip fetchByResult(String childEntity) throws EntityNotMappedException {
		return fetch(new DataSnip(null, childEntity, true, SnipType.SELECT, true));
	}

	public DataSnip fetchByResult(String joinAttribute, String childEntity, boolean joinFromChildIn) {
		return fetch(new DataSnip(joinAttribute, childEntity, joinFromChildIn, SnipType.SELECT, true));
	}

	public DataSnip fetch(String entity) {
		return fetch(new DataSnip(entity, SnipType.SELECT));
	}

	public DataSnip fetch(DataSnip dataSnip) {
		if (dataSnip != null) {
			fetchList.add(dataSnip);
			dataSnip.setParent(this);
			dataSnip.setDataManager(dataManager);
		}
		return dataSnip;
	}

	public void setParent(DataSnip parent) {
		this.parent = parent;
		if (parent == null) {
			parentId = null;
			return;
		}
		parentId = parent.getId();
	}

	@SuppressWarnings("nls")
	@Override
	public String toString() {
		return SnipUtils.toStringInt(this, 0);
	}

	public void setBundle(EntityBundle bundle) {
		if (this.bundle == null) {
			this.bundle = bundle;
			if (getSnipType().equals(SnipType.SELECT)) {
				columnNames = bundle.buildColumnsInt(this);
			}
		}
	}

	@Override
	public boolean equals(Object obj) {
		if (obj == null)
			return false;
		if (this == obj)
			return true;
		if (getClass() != obj.getClass())
			return false;
		DataSnip other = (DataSnip) obj;
		if (what == null) {
			if (other.what != null)
				return false;
		} else if (!what.equals(other.what))
			return false;
		if (where == null) {
			if (other.where != null)
				return false;
		} else if (!where.equals(other.where))
			return false;
		if (other.hashCode != hashCode)
			return false;
		return true;
	}

	@Override
	public int hashCode() {
		if (hashCode == -1) {
			hashCode = SnipUtils.buildHash(this);
		}
		return hashCode;
	}

	public DataSnip orderBy(String attrtibute, boolean orderASC) {
		extraAttribute = attrtibute;
		this.orderASC = orderASC;
		return this;
	}

	public DataSnip casheSnip(boolean value) {
		casheSnip = value;
		return this;
	}

	public void setDataManager(DataManager dataManager) {
		this.dataManager = dataManager;
	}

	public void create() {
		setSnipType(SnipType.CREATE);
		dataManager.execute(getHead());
	}

	public void drop() {
		setSnipType(SnipType.DROP);
		dataManager.execute(getHead());
	}

	public void insert() {
		setSnipType(SnipType.INSERT);
		dataManager.execute(getHead());
	}

	private DataSnip getHead() {
		if (parent != null) {
			return parent.getHead();
		}
		return this;
	}

	public void update() {
		setSnipType(SnipType.UPDATE);
		dataManager.execute(getHead());
	}

	public void delete() {
		setSnipType(SnipType.DELETE);
		dataManager.execute(getHead());
	}

	public Snip retrieve() {
		setSnipType(SnipType.SELECT);
		return dataManager.retrieve(getHead());
	}

	public <T> List<T> list() {
		setSnipType(SnipType.SELECT);
		return dataManager.instSnipList(getHead());
	}

	public <T> T first() {
		setSnipType(SnipType.SELECT);
		return dataManager.instSnip(getHead());
	}

	public void saveOrUpdate() {
		setSnipType(SnipType.FUNCTION);
		setFunction(Function.COUNT);
		int count = dataManager.count(getHead());
		if (count == 0) {
			setSnipType(SnipType.INSERT);
			dataManager.save(getHead());
		} else {
			setSnipType(SnipType.UPDATE);
			dataManager.update(getHead());
		}
	}

	public void saveIfNotExists() {
		setSnipType(SnipType.FUNCTION);
		setFunction(Function.COUNT);
		int count = dataManager.count(getHead());
		if (count == 0) {
			setSnipType(SnipType.INSERT);
			dataManager.save(getHead());
		}
	}

	public int count() {
		setFunction(Function.COUNT);
		return function();
	}

	protected int function() {
		setSnipType(SnipType.FUNCTION);
		Object[][] data = dataManager.retrieve(getHead()).getData();
		if (data.length > 0 && data[0].length > 0) {
			return Long.valueOf(data[0][0] + "").intValue();
		}
		return 0;
	}

	public double sum() {
		setFunction(Function.SUM);
		return function();
	}

	public double avg() {
		setFunction(Function.AVG);
		return function();
	}

	public double max() {
		setFunction(Function.MAX);
		return function();
	}

	public double min() {
		setFunction(Function.MIN);
		return function();
	}

	public SnipType getSnipType() {
		return snipType;
	}

	public void setSnipType(SnipType snipType) {
		this.snipType = snipType;
		if (parent != null) {
			parent.setSnipType(snipType);
		}
	}

	public Function getFunction() {
		return function;
	}

	public void setFunction(Function function) {
		this.function = function;
		if (parent != null) {
			parent.setFunction(function);
		}
	}

	public ChildPolicy getChildPolicy() {
		return childPolicy;
	}

	public DataSnip setChildPolicy(ChildPolicy childPolicy) {
		this.childPolicy = childPolicy;
		return this;
	}
}
